#pragma once

/*
  WString.h - String library for Wiring & Arduino
  ...mostly rewritten by Paul Stoffregen...
  Copyright (c) 2009-10 Hernando Barragan.  All right reserved.
  Copyright 2011, Paul Stoffregen, paul@pjrc.com

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

/*
  WString.cpp - String library for Wiring & Arduino
  ...mostly rewritten by Paul Stoffregen...
  Copyright (c) 2009-10 Hernando Barragan.  All rights reserved.
  Copyright 2011, Paul Stoffregen, paul@pjrc.com

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/


#include <ctype.h>
#include <stdlib.h>
#include <string.h>

#include "./heap.hpp"
#include "./pgm.hpp"
#include "./xtoa.hpp"


namespace arduino_basic {

    class __FlashStringHelper;

#define F(string_literal) (reinterpret_cast<const __FlashStringHelper *>(PSTR(string_literal)))


    // An inherited class for holding the result of a concatenation.  These
    // result objects are assumed to be writable by subsequent concatenations.
    class StringSumHelper;


    class String {
        // use a function pointer to allow for "if (s)" without the
        // complications of an operator bool(). for more information, see:
        // http://www.artima.com/cppsource/safebool.html
        typedef void (String::*StringIfHelperType)() const;
        void StringIfHelper() const {}

       public:
        // constructors
        // creates a copy of the initial value.
        // if the initial value is null or invalid, or if memory allocation
        // fails, the string will be marked as invalid (i.e. "if (s)" will
        // be false).
        String(const char *cstr = "") {
            init();
            if (cstr)
                copy(cstr, strlen(cstr));
        }

        String(const String &value) {
            init();
            *this = value;
        }

        String(const __FlashStringHelper *pstr) {
            init();
            *this = pstr;
        }

        String(String &&rval) {
            init();
            move(rval);
        }

        String(StringSumHelper &&rval) {
            init();
            move(rval);
        }

        explicit String(char c) {
            init();
            char buf[2];
            buf[0] = c;
            buf[1] = 0;
            *this = buf;
        }

        explicit String(unsigned char value, unsigned char base = 10) {
            init();
            WrapperToWriteCharBuffer<8 * sizeof(unsigned char)> w;
            write_unsigned_to_writer(w, value, base);
            *this = w.buffer();
        }

        explicit String(int value, unsigned char base = 10) {
            init();
            WrapperToWriteCharBuffer<8 * sizeof(int)> w;
            write_signed_to_writer(w, value, base);
            *this = w.buffer();
        }

        explicit String(unsigned int value, unsigned char base = 10) {
            init();
            WrapperToWriteCharBuffer<8 * sizeof(unsigned int)> w;
            write_unsigned_to_writer(w, value, base);
            *this = w.buffer();
        }

        explicit String(long value, unsigned char base = 10) {
            init();
            WrapperToWriteCharBuffer<8 * sizeof(long)> w;
            write_signed_to_writer(w, value, base);
            *this = w.buffer();
        }

        explicit String(unsigned long value, unsigned char base = 10) {
            init();
            WrapperToWriteCharBuffer<8 * sizeof(unsigned long)> w;
            write_unsigned_to_writer(w, value, base);
            *this = w.buffer();
        }

        explicit String(float value, unsigned char decimalPlaces = 2) {
            init();
            WrapperToWriteCharBuffer<32> w;
            write_float_to_writer(w, value, decimalPlaces);
            *this = w.buffer();
        }

        explicit String(double value, unsigned char decimalPlaces = 2) {
            init();
            WrapperToWriteCharBuffer<32> w;
            write_float_to_writer(w, value, decimalPlaces);
            *this = w.buffer();
        }

        ~String() {
            if (buffer)
                heap_free(buffer);
        }

        // memory management
        // return true on success, false on failure (in which case, the string
        // is left unchanged).  reserve(0), if successful, will validate an
        // invalid string (i.e., "if (s)" will be true afterwards)
        unsigned char reserve(unsigned int size) {
            if (buffer && capacity >= size)
                return 1;

            if (changeBuffer(size)) {
                if (len == 0) buffer[0] = 0;
                return 1;
            }
            return 0;
        }

        unsigned int length(void) const {
            return len;
        }

        // creates a copy of the assigned value.  if the value is null or
        // invalid, or if the memory allocation fails, the string will be
        // marked as invalid ("if (s)" will be false).
        String &operator=(const String &rhs) {
            if (this == &rhs)
                return *this;

            if (rhs.buffer)
                copy(rhs.buffer, rhs.len);
            else
                invalidate();

            return *this;
        }

        String &operator=(const char *cstr) {
            if (cstr)
                copy(cstr, strlen(cstr));
            else
                invalidate();

            return *this;
        }

        String &operator=(const __FlashStringHelper *pstr) {
            if (pstr)
                copy(pstr, strlen_P((PGM_P)pstr));
            else
                invalidate();

            return *this;
        }

        String &operator=(String &&rval) {
            if (this != &rval)
                move(rval);
            return *this;
        }

        String &operator=(StringSumHelper &&rval) {
            if (this != &rval)
                move(rval);
            return *this;
        }

        // concatenate (works w/ built-in types)
        // returns true on success, false on failure (in which case, the string
        // is left unchanged).  if the argument is null or invalid, the
        // concatenation is considered unsucessful.
        unsigned char concat(const String &s) {
            return concat(s.buffer, s.len);
        }

        unsigned char concat(const char *cstr) {
            if (!cstr)
                return 0;
            return concat(cstr, strlen(cstr));
        }

        unsigned char concat(char c) {
            char buf[2];
            buf[0] = c;
            buf[1] = 0;
            return concat(buf, 1);
        }

        unsigned char concat(unsigned char num) {
            return concat(static_cast<unsigned long>(num));
        }

        unsigned char concat(int num) {
            return concat(static_cast<long>(num));
        }

        unsigned char concat(unsigned int num) {
            return concat(static_cast<unsigned long>(num));
        }

        unsigned char concat(long num) {
            WrapperToWriteCharBuffer<8 * sizeof(long)> w;
            auto len = write_signed_to_writer(w, num, 10);
            return concat(w.buffer(), len);
        }

        unsigned char concat(unsigned long num) {
            WrapperToWriteCharBuffer<8 * sizeof(unsigned long)> w;
            auto len = write_unsigned_to_writer(w, num, 10);
            return concat(w.buffer(), len);
        }

        unsigned char concat(float num) {
            WrapperToWriteCharBuffer<20> w;
            auto len = write_float_to_writer(w, num, 2);
            return concat(w.buffer(), len);
        }

        unsigned char concat(double num) {
            WrapperToWriteCharBuffer<20> w;
            auto len = write_float_to_writer(w, num, 2);
            return concat(w.buffer(), len);
        }

        unsigned char concat(const __FlashStringHelper *str) {
            if (!str)
                return 0;

            int length = strlen_P((const char *)str);
            if (length == 0)
                return 1;

            unsigned int newlen = len + length;
            if (!reserve(newlen))
                return 0;

            strcpy_P(buffer + len, (const char *)str);
            len = newlen;
            return 1;
        }

        // if there's not enough memory for the concatenated value, the string
        // will be left unchanged (but this isn't signalled in any way)
        String &operator+=(const String &rhs) {
            concat(rhs);
            return (*this);
        }

        String &operator+=(const char *cstr) {
            concat(cstr);
            return (*this);
        }

        String &operator+=(char c) {
            concat(c);
            return (*this);
        }

        String &operator+=(unsigned char num) {
            concat(num);
            return (*this);
        }

        String &operator+=(int num) {
            concat(num);
            return (*this);
        }

        String &operator+=(unsigned int num) {
            concat(num);
            return (*this);
        }

        String &operator+=(long num) {
            concat(num);
            return (*this);
        }

        String &operator+=(unsigned long num) {
            concat(num);
            return (*this);
        }

        String &operator+=(float num) {
            concat(num);
            return (*this);
        }

        String &operator+=(double num) {
            concat(num);
            return (*this);
        }

        String &operator+=(const __FlashStringHelper *str) {
            concat(str);
            return (*this);
        }

        friend StringSumHelper &operator+(const StringSumHelper &lhs, const String &rhs);
        friend StringSumHelper &operator+(const StringSumHelper &lhs, const char *cstr);
        friend StringSumHelper &operator+(const StringSumHelper &lhs, char c);
        friend StringSumHelper &operator+(const StringSumHelper &lhs, unsigned char num);
        friend StringSumHelper &operator+(const StringSumHelper &lhs, int num);
        friend StringSumHelper &operator+(const StringSumHelper &lhs, unsigned int num);
        friend StringSumHelper &operator+(const StringSumHelper &lhs, long num);
        friend StringSumHelper &operator+(const StringSumHelper &lhs, unsigned long num);
        friend StringSumHelper &operator+(const StringSumHelper &lhs, float num);
        friend StringSumHelper &operator+(const StringSumHelper &lhs, double num);
        friend StringSumHelper &operator+(const StringSumHelper &lhs, const __FlashStringHelper *rhs);

        // comparison (only works w/ Strings and "strings")
        operator StringIfHelperType() const {
            return buffer ? &StringIfHelper : 0;
        }

        int compareTo(const String &s) const {
            if (!buffer || !s.buffer) {
                if (s.buffer && s.len > 0)
                    return 0 - *(unsigned char *)s.buffer;
                if (buffer && len > 0)
                    return *(unsigned char *)buffer;
                return 0;
            }
            return strcmp(buffer, s.buffer);
        }

        unsigned char equals(const String &s2) const {
            return (len == s2.len && compareTo(s2) == 0);
        }

        unsigned char equals(const char *cstr) const {
            if (len == 0)
                return (cstr == NULL || *cstr == 0);
            if (cstr == NULL)
                return buffer[0] == 0;
            return strcmp(buffer, cstr) == 0;
        }

        unsigned char operator==(const String &rhs) const {
            return equals(rhs);
        }

        unsigned char operator==(const char *cstr) const {
            return equals(cstr);
        }

        unsigned char operator!=(const String &rhs) const {
            return !equals(rhs);
        }

        unsigned char operator!=(const char *cstr) const {
            return !equals(cstr);
        }

        unsigned char operator<(const String &rhs) const {
            return compareTo(rhs) < 0;
        }

        unsigned char operator>(const String &rhs) const {
            return compareTo(rhs) > 0;
        }

        unsigned char operator<=(const String &rhs) const {
            return compareTo(rhs) <= 0;
        }

        unsigned char operator>=(const String &rhs) const {
            return compareTo(rhs) >= 0;
        }

        unsigned char equalsIgnoreCase(const String &s2) const {
            if (this == &s2)
                return 1;
            if (len != s2.len)
                return 0;
            if (len == 0)
                return 1;

            const char *p1 = buffer;
            const char *p2 = s2.buffer;

            while (*p1) {
                if (tolower(*p1++) != tolower(*p2++)) return 0;
            }
            return 1;
        }

        unsigned char startsWith(const String &s2) const {
            if (len < s2.len)
                return 0;
            return startsWith(s2, 0);
        }

        unsigned char startsWith(const String &s2, unsigned int offset) const {
            if (offset > len - s2.len || !buffer || !s2.buffer)
                return 0;
            return strncmp(&buffer[offset], s2.buffer, s2.len) == 0;
        }

        unsigned char endsWith(const String &s2) const {
            if (len < s2.len || !buffer || !s2.buffer)
                return 0;
            return strcmp(&buffer[len - s2.len], s2.buffer) == 0;
        }

        // character acccess
        char charAt(unsigned int loc) const {
            return operator[](loc);
        }

        void setCharAt(unsigned int loc, char c) {
            if (loc < len)
                buffer[loc] = c;
        }

        char operator[](unsigned int index) const {
            if (index >= len || !buffer)
                return 0;
            return buffer[index];
        }

        char &operator[](unsigned int index) {
            static char dummy_writable_char;
            if (index >= len || !buffer) {
                dummy_writable_char = 0;
                return dummy_writable_char;
            }
            return buffer[index];
        }

        void getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index) const {
            if (!bufsize || !buf)
                return;

            if (index >= len) {
                buf[0] = 0;
                return;
            }

            unsigned int n = bufsize - 1;
            if (n > len - index)
                n = len - index;

            strncpy((char *)buf, buffer + index, n);
            buf[n] = 0;
        }

        void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const {
            getBytes((unsigned char *)buf, bufsize, index);
        }

        const char *c_str() const {
            return buffer;
        }

        char *begin() {
            return buffer;
        }

        char *end() {
            return buffer + length();
        }

        const char *begin() const {
            return c_str();
        }

        const char *end() const {
            return c_str() + length();
        }

        // search
        int indexOf(char c) const {
            return indexOf(c, 0);
        }

        int indexOf(char ch, unsigned int fromIndex) const {
            if (fromIndex >= len)
                return -1;

            const char *temp = strchr(buffer + fromIndex, ch);
            if (temp == NULL)
                return -1;

            return temp - buffer;
        }

        int indexOf(const String &s2) const {
            return indexOf(s2, 0);
        }

        int indexOf(const String &s2, unsigned int fromIndex) const {
            if (fromIndex >= len)
                return -1;

            const char *found = strstr(buffer + fromIndex, s2.buffer);
            if (found == NULL)
                return -1;

            return found - buffer;
        }

        int lastIndexOf(char theChar) const {
            return lastIndexOf(theChar, len - 1);
        }

        int lastIndexOf(char ch, unsigned int fromIndex) const {
            if (fromIndex >= len)
                return -1;

            char tempchar = buffer[fromIndex + 1];
            buffer[fromIndex + 1] = '\0';
            char *temp = strrchr(buffer, ch);
            buffer[fromIndex + 1] = tempchar;

            if (temp == NULL)
                return -1;

            return temp - buffer;
        }

        int lastIndexOf(const String &s2) const {
            return lastIndexOf(s2, len - s2.len);
        }

        int lastIndexOf(const String &s2, unsigned int fromIndex) const {
            if (s2.len == 0 || len == 0 || s2.len > len)
                return -1;

            if (fromIndex >= len)
                fromIndex = len - 1;

            int found = -1;
            for (char *p = buffer; p <= buffer + fromIndex; p++) {
                p = strstr(p, s2.buffer);
                if (!p)
                    break;

                if ((unsigned int)(p - buffer) <= fromIndex)
                    found = p - buffer;
            }
            return found;
        }

        String substring(unsigned int beginIndex) const {
            return substring(beginIndex, len);
        };

        String substring(unsigned int left, unsigned int right) const {
            if (left > right) {
                unsigned int temp = right;
                right = left;
                left = temp;
            }

            String out;

            if (left >= len)
                return out;

            if (right > len)
                right = len;

            char temp = buffer[right];  // save the replaced character
            buffer[right] = '\0';
            out = buffer + left;   // pointer arithmetic
            buffer[right] = temp;  // restore character
            return out;
        }

        // modification
        void replace(char find, char replace) {
            if (!buffer)
                return;

            for (char *p = buffer; *p; p++) {
                if (*p == find)
                    *p = replace;
            }
        }

        void replace(const String &find, const String &replace) {
            if (len == 0 || find.len == 0)
                return;

            int diff = replace.len - find.len;
            char *readFrom = buffer;
            char *foundAt;
            if (diff == 0) {
                while ((foundAt = strstr(readFrom, find.buffer)) != NULL) {
                    memcpy(foundAt, replace.buffer, replace.len);
                    readFrom = foundAt + replace.len;
                }
            }
            else if (diff < 0) {
                char *writeTo = buffer;
                while ((foundAt = strstr(readFrom, find.buffer)) != NULL) {
                    unsigned int n = foundAt - readFrom;
                    memcpy(writeTo, readFrom, n);
                    writeTo += n;
                    memcpy(writeTo, replace.buffer, replace.len);
                    writeTo += replace.len;
                    readFrom = foundAt + find.len;
                    len += diff;
                }
                strcpy(writeTo, readFrom);
            }
            else {
                unsigned int size = len;  // compute size needed for result
                while ((foundAt = strstr(readFrom, find.buffer)) != NULL) {
                    readFrom = foundAt + find.len;
                    size += diff;
                }

                if (size == len)
                    return;

                if (size > capacity && !changeBuffer(size))
                    return;  // XXX: tell user!

                int index = len - 1;
                while (index >= 0 && (index = lastIndexOf(find, index)) >= 0) {
                    readFrom = buffer + index + find.len;
                    memmove(readFrom + diff, readFrom, len - (readFrom - buffer));
                    len += diff;
                    buffer[len] = 0;
                    memcpy(buffer + index, replace.buffer, replace.len);
                    index--;
                }
            }
        }

        void remove(unsigned int index) {
            // Pass the biggest integer as the count. The remove method
            // below will take care of truncating it at the end of the
            // string.
            remove(index, (unsigned int)-1);
        }

        void remove(unsigned int index, unsigned int count) {
            if (index >= len) {
                return;
            }

            if (count <= 0) {
                return;
            }

            if (count > len - index) {
                count = len - index;
            }

            char *writeTo = buffer + index;
            len = len - count;
            strncpy(writeTo, buffer + index + count, len - index);
            buffer[len] = 0;
        }

        void toLowerCase(void) {
            if (!buffer)
                return;

            for (char *p = buffer; *p; p++) {
                *p = tolower(*p);
            }
        }

        void toUpperCase(void) {
            if (!buffer)
                return;

            for (char *p = buffer; *p; p++) {
                *p = toupper(*p);
            }
        }

        void trim(void) {
            if (!buffer || len == 0)
                return;

            char *begin = buffer;
            while (isspace(*begin))
                begin++;

            char *end = buffer + len - 1;
            while (isspace(*end) && end >= begin)
                end--;

            len = end + 1 - begin;
            if (begin > buffer)
                memcpy(buffer, begin, len);

            buffer[len] = 0;
        }

        // parsing/conversion
        long toInt(void) const {
            if (buffer)
                return atol(buffer);
            return 0;
        }

        float toFloat(void) const {
            return float(toDouble());
        }

        double toDouble(void) const {
            if (buffer)
                return atof(buffer);
            return 0;
        }

       protected:
        char *buffer;           // the actual char array
        unsigned int capacity;  // the array length minus one (for the '\0')
        unsigned int len;       // the String length (not counting the '\0')

       protected:
        void init(void) {
            buffer = NULL;
            capacity = 0;
            len = 0;
        }

        void invalidate(void) {
            if (buffer)
                heap_free(buffer);
            buffer = NULL;
            capacity = len = 0;
        }

        unsigned char changeBuffer(unsigned int maxStrLen) {
            char *newbuffer = (char *)heap_realloc(buffer, maxStrLen + 1);
            if (newbuffer) {
                buffer = newbuffer;
                capacity = maxStrLen;
                return 1;
            }
            return 0;
        }

        unsigned char concat(const char *cstr, unsigned int length) {
            unsigned int newlen = len + length;
            if (!cstr)
                return 0;

            if (length == 0)
                return 1;

            if (!reserve(newlen))
                return 0;

            strcpy(buffer + len, cstr);
            len = newlen;
            return 1;
        }

        // copy and move
        String &copy(const char *cstr, unsigned int length) {
            if (!reserve(length)) {
                invalidate();
                return *this;
            }
            len = length;
            strcpy(buffer, cstr);
            return *this;
        }

        String &copy(const __FlashStringHelper *pstr, unsigned int length) {
            if (!reserve(length)) {
                invalidate();
                return *this;
            }
            len = length;
            strcpy_P(buffer, (PGM_P)pstr);
            return *this;
        }

        void move(String &rhs) {
            if (buffer) {
                if (rhs && capacity >= rhs.len) {
                    strcpy(buffer, rhs.buffer);
                    len = rhs.len;
                    rhs.len = 0;
                    return;
                }
                else {
                    heap_free(buffer);
                }
            }
            buffer = rhs.buffer;
            capacity = rhs.capacity;
            len = rhs.len;
            rhs.buffer = NULL;
            rhs.capacity = 0;
            rhs.len = 0;
        }
    };


    class StringSumHelper : public String {
       public:
        StringSumHelper(const String &s) :
            String(s) {}

        StringSumHelper(const char *p) :
            String(p) {}

        StringSumHelper(char c) :
            String(c) {}

        StringSumHelper(unsigned char num) :
            String(num) {}

        StringSumHelper(int num) :
            String(num) {}

        StringSumHelper(unsigned int num) :
            String(num) {}

        StringSumHelper(long num) :
            String(num) {}

        StringSumHelper(unsigned long num) :
            String(num) {}

        StringSumHelper(float num) :
            String(num) {}

        StringSumHelper(double num) :
            String(num) {}
    };


    inline StringSumHelper &operator+(const StringSumHelper &lhs, const String &rhs) {
        StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
        if (!a.concat(rhs.buffer, rhs.len))
            a.invalidate();
        return a;
    }

    inline StringSumHelper &operator+(const StringSumHelper &lhs, const char *cstr) {
        StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
        if (!cstr || !a.concat(cstr, strlen(cstr)))
            a.invalidate();
        return a;
    }

    inline StringSumHelper &operator+(const StringSumHelper &lhs, char c) {
        StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
        if (!a.concat(c))
            a.invalidate();
        return a;
    }

    inline StringSumHelper &operator+(const StringSumHelper &lhs, unsigned char num) {
        StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
        if (!a.concat(num))
            a.invalidate();
        return a;
    }

    inline StringSumHelper &operator+(const StringSumHelper &lhs, int num) {
        StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
        if (!a.concat(num))
            a.invalidate();
        return a;
    }

    inline StringSumHelper &operator+(const StringSumHelper &lhs, unsigned int num) {
        StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
        if (!a.concat(num))
            a.invalidate();
        return a;
    }

    inline StringSumHelper &operator+(const StringSumHelper &lhs, long num) {
        StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
        if (!a.concat(num))
            a.invalidate();
        return a;
    }

    inline StringSumHelper &operator+(const StringSumHelper &lhs, unsigned long num) {
        StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
        if (!a.concat(num))
            a.invalidate();
        return a;
    }

    inline StringSumHelper &operator+(const StringSumHelper &lhs, float num) {
        StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
        if (!a.concat(num))
            a.invalidate();
        return a;
    }

    inline StringSumHelper &operator+(const StringSumHelper &lhs, double num) {
        StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
        if (!a.concat(num))
            a.invalidate();
        return a;
    }

    inline StringSumHelper &operator+(const StringSumHelper &lhs, const __FlashStringHelper *rhs) {
        StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
        if (!a.concat(rhs))
            a.invalidate();
        return a;
    }

}  // namespace arduino_basic